﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using MacomberMapSystem.Common.Database;
using Ionic.Zip;
using System.IO;
using System.Data.Common;
using Ionic.Zlib;
using MacomberMapSystem.Common.Serialization;
using System.Data;

namespace MacomberMapSystem.Common.Processing
{
    /// <summary>
    /// This class is responsible for backing up a database
    /// </summary>
    public class MM_Backup_Database : CIM_Processor
    {
        #region Variable declarations
        /// <summary>The path to our output file</summary>
        private String FilePath;

        #endregion

        #region Initialization
        /// <summary>
        /// Initiate a new backup database process
        /// </summary>
        /// <param name="xElem"></param>
        /// <param name="NamedParameters"></param>
        /// <param name="Repository"></param>
        public MM_Backup_Database(XmlElement xElem, Dictionary<String, Object> NamedParameters, MM_Repository Repository)
            : base(xElem, NamedParameters, Repository)
        { }

        /// <summary>
        /// Report the name of our process
        /// </summary>
        public override string ProcessName
        {
            get { return "Backup database"; }
        }

        /// <summary>
        /// Execute our datbase backup
        /// </summary>
        /// <param name="FileName"></param>
        /// <param name="DbConn"></param>
        public void InitiateProcess(String FileName, MM_Database_Connector DbConn)
        {
            FilePath = FileName;
            InitiateProcessInPooledThread(DbConn);
        }

        /// <summary>
        /// Execute our database backup
        /// </summary>
        /// <param name="DbConn"></param>
        public override enumProcessState InitiateProcess(MM_Database_Connector DbConn)
        {
            if (FilePath.EndsWith(".zip", StringComparison.CurrentCultureIgnoreCase))
                using (FileStream fS = new FileStream(FilePath, FileMode.Create))
                using (ZipOutputStream zS = new ZipOutputStream(fS))
                {                    
                    zS.CompressionLevel = Ionic.Zlib.CompressionLevel.BestCompression;
                    zS.Comment = "Exported by " + Environment.UserDomainName + "\\" + Environment.UserName + " at " + DateTime.Now.ToString();
                    zS.ProvisionalAlternateEncoding = Encoding.UTF8;
                    zS.UseUnicodeAsNecessary = true;                    
                    zS.PutNextEntry("MacomberMapLoaderDatabase.xml");
                    using (XmlTextWriter xW = BackupDatabaseToStream(zS))
                        xW.Flush();
                    zS.Flush();
                    zS.Close();

                }
            else if (FilePath.EndsWith(".gz"))
                using (FileStream fS = new FileStream(FilePath, FileMode.Create))
                using (GZipStream gS = new GZipStream(fS, CompressionMode.Compress, CompressionLevel.BestCompression))
                using (XmlTextWriter xW = BackupDatabaseToStream(gS))
                    xW.Flush();
            else if (FilePath.EndsWith(".xml"))
                using (FileStream fS = new FileStream(FilePath, FileMode.Create))
                using (XmlTextWriter xW = BackupDatabaseToStream(fS))
                    xW.Flush();
            return enumProcessState.Completed;
        }
        #endregion

        #region Exporting

        /// <summary>
        /// Write our database backup to a stream
        /// </summary>
        /// <param name="TargetStream">The stream to which the data should be written</param>  
        private XmlTextWriter BackupDatabaseToStream(Stream TargetStream)
        {
            XmlTextWriter xW = new XmlTextWriter(TargetStream, new UTF8Encoding(false));
            xW.Formatting = Formatting.Indented;



            xW.WriteStartDocument();
            xW.WriteStartElement("Database");
            xW.WriteAttributeString("Server", Repository.Db.Database.ConnectionString);
            xW.WriteAttributeString("ExportedBy", Environment.UserDomainName + "\\" + Environment.UserName);
            xW.WriteAttributeString("ExportedOn", XmlConvert.ToString(DateTime.Now, XmlDateTimeSerializationMode.Unspecified));

            //Now, write out every sequence
            using (DbCommand dCmd = Repository.Db.CreateCommand("SELECT * FROM USER_SEQUENCES"))
            using (DbDataReader dRd = dCmd.ExecuteReader())
                while (dRd.Read())
                    WriteTableInfo(xW, dRd, "Sequence");

            xW.Flush();

            //Now, write out every table
            List<String> TableToProcess = new List<string>();
            using (DbCommand dCmd = Repository.Db.CreateCommand("SELECT * FROM USER_TABLES"))
            using (DbDataReader dRd = dCmd.ExecuteReader())
                while (dRd.Read())
                {
                    Total += Convert.ToInt32(dRd["Num_Rows"]);
                    WriteTableInfo(xW, dRd, "Table");
                    TableToProcess.Add((string)dRd["Table_Name"]);
                    
                }

            foreach (String str in TableToProcess)
            {               
                using (DbCommand dCmd2 = Repository.Db.CreateCommand("SELECT * FROM " + str))
                using (DbDataReader dRd2 = dCmd2.ExecuteReader())
                    while (dRd2.Read())
                    {
                        WriteTableInfo(xW, dRd2, "Row");
                        Current++;
                    }
                xW.Flush();
            }
            xW.WriteEndDocument();
            xW.Flush();
            return xW;
        }

        /// <summary>
        /// Write out a table's information
        /// </summary>
        /// <param name="xW"></param>
        /// <param name="dRd"></param>
        /// <param name="Title"></param>
        private void WriteTableInfo(XmlTextWriter xW, DbDataReader dRd, String Title)
        {
            xW.WriteStartElement(Title);
            for (int a = 0; a < dRd.FieldCount; a++)
                if (dRd[a] is DBNull == false)
                    WriteAttribute(xW, dRd.GetName(a), dRd[a], dRd.GetDataTypeName(a));

            //Now, create our DDL for the table
            using (DbCommand dCmd2 = Repository.Db.CreateCommand("SELECT DBMS_METADATA.GET_DDL(:0, :1) FROM DUAL", Title, dRd["TABLE_NAME"]))
                xW.WriteElementString("DDL", dCmd2.ExecuteScalar().ToString());

            //Now, write out our columns and thier names
            if (Title.Equals("Table", StringComparison.CurrentCultureIgnoreCase))
            {
                xW.WriteStartElement("Columns");
                using (DbCommand dCmd2 = Repository.Db.CreateCommand("DESCRIBE " + dRd["TABLE_NAME"].ToString()))
                using (DbDataReader dRd2 = dCmd2.ExecuteReader())
                    while (dRd2.Read())
                    {
                        xW.WriteStartElement("Column");
                        for (int a = 0; a < dRd2.FieldCount; a++)
                            xW.WriteAttributeString(dRd2.GetName(a), dRd.GetValue(a).ToString());
                        xW.WriteEndElement();
                    }
            }
            xW.WriteEndElement();
        }

        /// <summary>
        /// Write out an attribute
        /// </summary>
        /// <param name="xW"></param>
        /// <param name="Name"></param>
        /// <param name="OutVal"></param>
        /// <param name="TypeName"></param>
        private void WriteAttribute(XmlTextWriter xW, String Name, object OutVal, String TypeName)
        {
            if (TypeName == "VARCHAR2")
                xW.WriteAttributeString(Name, (string)OutVal);
            else if (TypeName == "NUMBER")
                xW.WriteAttributeString(Name, OutVal.ToString());
            else if (TypeName == "DATE" || TypeName == "TIMESTAMP")
                xW.WriteAttributeString(Name, XmlConvert.ToString((DateTime)OutVal, XmlDateTimeSerializationMode.Unspecified));
            else if (TypeName == "CLOB")
                xW.WriteAttributeString(Name, Convert.ToBase64String(new UTF8Encoding(false).GetBytes((string)OutVal)));
            else if (TypeName == "BLOB")
                xW.WriteAttributeString(Name, Convert.ToBase64String((byte[])OutVal));
        }
        #endregion

    }
}